-
Notifications
You must be signed in to change notification settings - Fork 416
Introduce Stronger Typing for VerifiedInvoiceRequest
and Refactor Invoice Building Flow
#3964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
👋 I see @valentinewallace was un-assigned. |
cc @jkczyz |
🔔 1st Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 1st Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 3rd Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 4th Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 3rd Reminder Hey @jkczyz @valentinewallace! This PR has been waiting for your review. |
🔔 5th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3964 +/- ##
==========================================
- Coverage 88.85% 88.77% -0.09%
==========================================
Files 175 175
Lines 127710 127980 +270
Branches 127710 127980 +270
==========================================
+ Hits 113478 113608 +130
- Misses 11675 11806 +131
- Partials 2557 2566 +9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🔔 6th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 7th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 8th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 9th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 10th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
🔔 11th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
I'm unfortunately not going to have time to review this in a timely manner, so unassigning myself and letting the bot take the wheel |
✅ Added second reviewer: @joostjager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After an initial pass, my feelings about this PR are mixed. Like probably every other rust dev, I also like strong types and compile time checks. But I am not sure if the old situation with the keys option was a big enough problem to justify the code changes proposed in this PR. Especially because the end result may not be considered fully clean either (panic match arms, code duplication).
Other considerations are dev/review resources available, risk that every change introduces and priority. Perhaps it would be good for this PR (and PRs in general) to add a few lines to the description elaborating on that. Could be downstream advantages for example that aren't visible in the PR itself.
@@ -961,9 +961,43 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( | |||
} | |||
} } | |||
|
|||
macro_rules! fields_accessor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My impression is that we want to get rid of macros as much as possible, not introduce new ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointer, Joost!
I went with a macro here to avoid duplicating the same function three times across different structs. It felt like a minimally intrusive way to solve the problem while keeping things consistent with the surrounding code.
That said, I agree we could move away from macros in the long term. Removing this one (and similar ones — e.g. in invoice_request.rs
) probably makes more sense as part of a broader refactor, which I think is best handled in a separate PR.
Let me know what you think!
}, | ||
}; | ||
let payment_paths = self | ||
.create_blinded_payment_paths( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not ideal that code needs to be duplicated now.
Final thought, not fully checked, is whether there is a simpler way to make the api safe by just moving the respond methods from |
Agreed that the PR description should include motivation for the change. Namely, use for custom handling of invoice requests / building custom invoices. |
Can I see this somewhere? So that it becomes more clear where the benefits are of the stronger typing. |
Thanks for the thoughtful review, Joost! I’m working through your points and will follow up with concrete changes soon. In the meantime, I’ve updated the PR description to explain the motivation a bit more, especially around the manual invoice request handling mentioned by Jeff. Would love your thoughts when you get a chance. |
In the following commits we will introduce `fields` function for other types as well, so to keep code DRY we convert the function to a macro.
This commit reintroduces `VerifiedInvoiceRequest`, now parameterized by `SigningPubkeyStrategy`. The key motivation is to restrict which functions can be called on a `VerifiedInvoiceRequest` based on its strategy type. This enables compile-time guarantees — ensuring that an incorrect `InvoiceBuilder` cannot be constructed for a given request, and misuses are caught early.
This change improves type safety and architectural clarity by introducing dedicated `InvoiceBuilder` methods tied to each variant of `VerifiedInvoiceRequestEnum`. With this change, users are now required to match on the enum variant before calling the corresponding builder method. This pushes the responsibility of selecting the correct builder to the user and ensures that invalid builder usage is caught at compile time, rather than relying on runtime checks. The signing logic has also been moved from the builder to the `ChannelManager`. This shift simplifies the builder's role and aligns it with the rest of the API, where builder methods return a configurable object that can be extended before signing. The result is a more consistent and predictable interface that separates concerns cleanly and makes future maintenance easier.
To ensure correct Bolt12 payment flow behavior, the `amount_msats` used for generating the `payment_hash`, `payment_secret`, and payment path must remain consistent. Previously, these steps could inadvertently diverge due to separate sources of `amount_msats`. This commit refactors the interface to use a `get_payment_info` closure, which captures the required variables and provides a single source of truth for both payment info (payment_hash, payment_secret) and path generation. This ensures consistency and eliminates subtle bugs that could arise from mismatched amounts across the flow.
Just to be sure that I am looking at the benefits in the right location, I posted #3833 (comment) |
Updated from pr3964.04 to pr3964.05 (diff): Changes:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HIgh-level ack, although I remain doubtful of the cost/benefit of this PR. A runtime check isn't the end of the world, especially if it isn't hard to hit the case during testing.
The end result isn't completely ideal either, taking some away from the benefit side of the scale:
- Enum + generic feels a bit redundant, even though I understand the reason for doing it
- Code duplication
- Need for macros which we generally want to get rid of
Will leave full final review to @jkczyz
This PR refactors how invoice building works from a
VerifiedInvoiceRequest
, replacing runtime checks with compile-time guarantees. Previously, invoice builder selection (betweenusing_derived_key
andusing_explicit_key
) was based on an optionalkey
field, which left room for misuse and bugs that could only be caught at runtime.To address this,
VerifiedInvoiceRequest
is now parameterized by aSigningPubkeyStrategy
, exposing only the appropriate builder method for each variant. This enforces correctness at the type level and prevents invalid combinations from compiling.Additional changes:
OffersMessageFlow
interface now uses distinct builder paths based on theVerifiedInvoiceRequest
variant, shifting variant matching to compile time.ChannelManager
: Aligns with existing patterns and gives users flexibility to customize theInvoiceBuilder
before signing.(payment_hash, payment_secret)
creation and consolidatesamount_msats
into a single authoritative source.Reasoning:
This change lays the groundwork for #3833, which introduces Flow events to support manual handling of
OffersMessages
.Invoice building from an
InvoiceRequest
depends on aSigningPubkeyStrategy
, which can be either:DerivedSigningPubkey
: the key is derived from the offer inside the request, orExplicitSigningPubkey
: the key is supplied by the user when signing the final invoice.Previously, we relied on runtime checks to validate builder–strategy alignment. While this worked, it lacked compile-time safety—invalid combinations could still compile and only fail at runtime.
This was acceptable under the previous model, where users couldn’t build invoices manually (e.g., when using the default
OffersMessageFlow
withChannelManager
). But with the Flow event API introduced in #3833, users will now handle invoice requests asynchronously and construct invoices directly.With this PR, only the valid builder–strategy pairs are allowed at compile time. This improves type safety, prevents incorrect usage, and makes the manual invoice-generation flow more robust as LDK evolves.